package core

import (
	"bytes"
	"context"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"github.com/golang-jwt/jwt/v5"
	"github.com/imroc/req/v3"
	"github.com/nfnt/resize"
	"github.com/shirou/gopsutil/host"
	"golang.org/x/image/webp"
	"gorm.io/gorm"
	"image"
	"image/jpeg"
	"io"
	"myai/core/types"
	"myai/store/model"
	"myai/utils"
	"myai/utils/resp"
	"net/http"
	"os"
	"runtime/debug"
	"strings"
	"time"
)

type AppServer struct {
	Debug     bool
	Config    *types.AppConfig    // 应用配置
	Engine    *gin.Engine         // gin 引擎
	SysConfig *types.SystemConfig // 系统配置
}

func NewServer(appConfig *types.AppConfig) *AppServer {
	// gin.SetMode(gin.ReleaseMode)是一个用于设置Gin框架运行模式的函数。将Gin框架的运行模式设置为ReleaseMode可以关闭日志输出
	gin.SetMode(gin.ReleaseMode)
	// gin.DefaultWriter 控制日志输出
	// io.Discard 是一个空的写入器，会丢弃所有写入的数据
	gin.DefaultWriter = io.Discard
	return &AppServer{
		Debug:  false,
		Config: appConfig,
		Engine: gin.Default(),
	}
}

func (s *AppServer) Init(debug bool, client *redis.Client) {
	s.Engine.Use(corsMiddleware())
	s.Engine.Use(staticResourceMiddleware())
	s.Engine.Use(authorizeMiddleware(s, client))
	s.Engine.Use(parameterHandlerMiddleware())
	s.Engine.Use(errorHandler)
	// 添加静态资源访问
	s.Engine.Static("/static", s.Config.StaticDir)
	s.Debug = debug
}

func (s *AppServer) Run(db *gorm.DB) error {
	var sysConfig model.Config
	// 查询系统配置信息
	err := db.Where("marker", "system").First(&sysConfig).Error
	if err != nil {
		return fmt.Errorf("查询系统配置信息出现错误：%v", err)
	}
	// 把查询处理的系统配置信息设置到 SystemConfig 中
	err = utils.JsonDecode(sysConfig.Config, &s.SysConfig)
	if err != nil {
		return fmt.Errorf("无法解码系统配置数据：%v", err)
	}
	// 打印服务启动地址
	log.Infof("http://%s", s.Config.Listen)
	// 统计安装信息
	go func() {
		// 获取当前主机详细信息
		info, err := host.Info()
		if err == nil {
			apiURL := fmt.Sprintf("%s/%s", s.Config.ApiConfig.ApiURL, "/api/installs/push")
			timestamp := time.Now().Unix()
			product := "geekai-plus"
			signStr := fmt.Sprintf("%s#%s#%d", product, info.HostID, timestamp)
			sign := utils.Sha256(signStr)
			// 发送请求
			resp, err := req.C().R().SetBody(map[string]interface{}{"product": product, "device_id": info.HostID, "timestamp": timestamp, "sign": sign}).Post(apiURL)
			if err != nil {
				log.Errorf("注册安装信息失败：%v", err)
			} else {
				log.Debugf("注册安装信息成功：%v", resp.String())
			}
		}
	}()
	// 启动服务
	return s.Engine.Run(s.Config.Listen)
}

// 跨域中间件设置
func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取请求方法
		method := c.Request.Method
		origin := c.Request.Header.Get("Origin")
		if origin != "" {
			// 设置允许的请求源
			// 允许请求来源的 Origin 直接作为响应头值，开发环境友好但生产环境存在安全风险（建议限制为白名单域名
			c.Header("Access-Control-Allow-Origin", origin)
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
			// 允许跨域设置可以返回其他子段，可以自定义字段
			// 自定义 Admin-Authorization 等业务头部的跨域支持，需与前端配合使用
			c.Header("Access-Control-Allow-Headers", "Authorization, Body-Length, Body-Type, Admin-Authorization,content-type")
			// 允许浏览器（客户端）可以解析的头部 （重要）
			c.Header("Access-Control-Expose-Headers", "Body-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
			// 设置缓存时间
			// 设置 172800 秒（48 小时）预检缓存，减少 OPTIONS 请求频率
			c.Header("Access-Control-Max-Age", "172800")
			// 允许客户端传递校验信息比如 cookie (重要)
			// Allow-Credentials: true 支持 Cookie/Token 认证，但需注意与 Allow-Origin: * 的互斥性
			c.Header("Access-Control-Allow-Credentials", "true")
		}
		// 针对 OPTIONS 方法直接返回 200 响应，符合 CORS 预检请求规范
		if method == http.MethodOptions {
			c.JSON(http.StatusOK, "ok!")
		}
		defer func() {
			if err := recover(); err != nil {
				log.Infof("Panic info is：%v", err)
			}
		}()
		c.Next()
	}
}

// 用户授权验证
func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Sec-WebSocket-Protocol：WebSocket 连接专用头，用于协商子协议（如 realtime 或 token）。
		clientProtocols := c.GetHeader("Sec-WebSocket-Protocol")
		var tokenString string
		// 判断是否是后台管理API
		isAdminApi := strings.Contains(c.Request.URL.Path, "/api/admin/")
		if isAdminApi {
			// 后台管理 API：从自定义 Header提取 Token
			tokenString = c.GetHeader(types.AdminAuthHeader)
		} else if clientProtocols != "" { // WebSocket 连接
			// 解析子协议内容
			protocols := strings.Split(clientProtocols, ",")
			// realtime 协议：截取特定位置 Token
			if protocols[0] == "realtime" {
				tokenString = strings.TrimSpace(protocols[1][25:])
			} else if protocols[0] == "token" { // token 协议：直接提取子协议值
				tokenString = strings.TrimSpace(protocols[1])
			}
		} else {
			// 普通用户 API：从标准 Header（如 Authorization）提取 Token
			tokenString = c.GetHeader(types.UserAuthHeader)
		}
		if tokenString == "" {
			// 判断请求是否能够放行
			if needLogin(c) {
				resp.NotAuth(c, "您应该在请求标头中添加授权")
				c.Abort()
				return
			} else {
				// 放行
				c.Next()
				return
			}
		}
		// token 解析
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			// 强制使用 HMAC，防止非对称算法攻击
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok && needLogin(c) {
				return nil, fmt.Errorf("unexpected signing method：%v", token.Header["alg"])
			}
			// 管理员接口使用独立密钥（AdminSession.SecretKey）
			if isAdminApi {
				return []byte(s.Config.AdminSession.SecretKey), nil
			} else {
				// 普通用户接口使用通用密钥（Session.SecretKey）
				return []byte(s.Config.Session.SecretKey), nil
			}
		})
		if err != nil && needLogin(c) {
			resp.NotAuth(c, fmt.Sprintf("认证失败，令牌解析错误：%v", err))
			c.Abort()
			return
		}
		// Claim 结构验证：确保 Token 声明为 jwt.MapClaims 类型。
		claims, ok := token.Claims.(jwt.MapClaims)
		if !ok || !token.Valid && needLogin(c) {
			resp.NotAuth(c, "令牌无效")
			c.Abort()
			return
		}
		// 获取令牌过期时间
		expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
		// 判断令牌是否已经过期
		if expr > 0 && int64(expr) < time.Now().Unix() && needLogin(c) {
			resp.NotAuth(c, "令牌已经过期")
			c.Abort()
			return
		}
		key := fmt.Sprintf("users/%v", claims["user_id"])
		if isAdminApi {
			key = fmt.Sprintf("admin/%v", claims["user_id"])
		}
		// 从 Redis 中获取令牌
		if _, err := client.Get(context.Background(), key).Result(); err != nil && needLogin(c) {
			resp.NotAuth(c, "在缓存中找不到令牌，请重新登录")
			c.Abort()
			return
		}
		// 设置用户ID
		c.Set(types.LoginUserID, claims["user_id"])
	}
}

// 统一参数处理
func parameterHandlerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// GET请求参数处理
		// 解析 URL 的查询参数为 url.Values 类型（map[string][]string）
		params := c.Request.URL.Query()
		for key, values := range params {
			for i, value := range values {
				// 去除字符串首尾空格
				params[key][i] = strings.TrimSpace(value)
			}
		}
		// 将修改后的参数重新编码为 URL 查询字符串，更新 RawQuery
		c.Request.URL.RawQuery = params.Encode()
		// 获取请求头信息
		contentType := c.Request.Header.Get("Content-Type")
		// multipart/form-data 类型请求的处理（通常是文件上传）
		if strings.Contains(contentType, "multipart/form-data") {
			c.Next()
			return
		}
		// POST 请求一般传递的参数都是JSON格式
		if strings.Contains(contentType, "application/json") {
			// 一次性读取请求体内容
			bodyBytes, err := io.ReadAll(c.Request.Body)
			if err != nil {
				c.Next()
				return
			}
			// 还原请求体
			// 将 []byte 转换为可重复读取的 io.ReadCloser
			c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
			// 将请求体解析为JSON
			var jsonData map[string]interface{}
			// c.ShouldBindJSON：将 JSON 数据绑定到 map[string]interface{}（底层调用 json.Unmarshal）
			if err := c.ShouldBindJSON(&jsonData); err != nil {
				c.Next()
				return
			}
			// 对json数据中的字符串值去除两端的空格
			trimJSONStrings(jsonData)
			// 更新请求体
			c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
		}
		c.Next()
	}
}

// 静态资源中间件，请求图片资源会用到该中间件
func staticResourceMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取请求URL地址
		url := c.Request.URL.String()
		// 拦截生成缩略图的请求
		// 匹配 /static/ 路径且包含 imageView2 参数的请求
		if strings.HasPrefix(url, "/static/") && strings.Contains(url, "?imageView2") {
			// 会在指定的分隔符之后分割字符串，并保留分隔符本身
			// url := "/static/photo.jpg?imageView2/1/w/200/h/300"
			// 输出: ["/static/photo.jpg?imageView2", "/1/w/200/h/300"]
			r := strings.SplitAfter(url, "imageView2")
			size := strings.Split(r[1], "/")
			if len(size) != 8 {
				c.String(http.StatusNotFound, "invalid thumb args")
				return
			}
			width := utils.IntValue(size[3], 0)    // 图片宽度
			height := utils.IntValue(size[5], 0)   // 图片高度
			quality := utils.IntValue(size[7], 75) // 图片质量
			// 获取图片地址
			// strings.TrimLeft 会从字符串的左侧（开头）删除所有属于指定字符集合的字符
			// path := "//static/img.jpg"
			// 输出 "static/img.jpg"
			filePath := strings.TrimLeft(c.Request.URL.Path, "/")
			// 打开文件
			file, err := os.Open(filePath)
			if err != nil {
				c.String(http.StatusNotFound, "Image not found")
				return
			}
			defer file.Close()
			// 解码图片，自动识别并解码常见格式（JPEG/PNG/GIF/BMP）
			img, _, err := image.Decode(file)
			if err != nil {
				// 解码WebP格式图像
				img, err = webp.Decode(file)
			}
			if err != nil {
				c.String(http.StatusInternalServerError, "图片解码失败")
				return
			}
			var newImg image.Image
			// width或height为0时：调用resize.Resize，固定非零维度，另一维度按比例自适应。
			// 例如，若width=800且height=0，则按宽度800等比缩放高度
			if height == 0 || width == 0 {
				// 固定宽度和高度自适应
				newImg = resize.Resize(uint(width), uint(height), img, resize.Lanczos3)
			} else {
				// 生成缩略图
				newImg = resize.Thumbnail(uint(width), uint(height), img, resize.Lanczos3)
			}
			var buffer bytes.Buffer
			// JPEG编码与压缩
			// Quality：压缩质量（范围1-100），数值越低压缩率越高但画质越差。例如Quality: 80是常用平衡点
			// buffer：使用bytes.Buffer作为内存缓冲区存储编码后的JPEG二进制数据，避免磁盘I/O开销
			err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality})
			if err != nil {
				log.Error(err)
				c.String(http.StatusInternalServerError, err.Error())
				return
			}
			// 设置图片缓存有效期为一年（365天）
			c.Header("Cache-Control", "max-age=31536000, public")
			// 直接输出图像数据流
			c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
			c.Abort() // 中断请求
		}
		c.Next()
	}
}

// 递归对 JSON 数据中的字符串值去除两端空格
func trimJSONStrings(data interface{}) {
	switch v := data.(type) { // 获取data的数据类型
	case map[string]interface{}: // 判断是否是map类型
		for key, val := range v {
			switch valueType := val.(type) { // 获取value的数据类型
			case string:
				v[key] = strings.TrimSpace(valueType)
			case map[string]interface{}, []interface{}:
				trimJSONStrings(val)
			}
		}
	case []interface{}: // 如果是切片
		for i, value := range v {
			switch valueType := value.(type) {
			case string:
				v[i] = strings.TrimSpace(valueType)
			case map[string]interface{}, []interface{}:
				trimJSONStrings(value)
			}
		}
	}
}

// 判断接口访问是否需要登录
func needLogin(c *gin.Context) bool {
	if c.Request.URL.Path == "/api/user/login" ||
		c.Request.URL.Path == "/api/user/logout" ||
		c.Request.URL.Path == "/api/user/reset_pass" ||
		c.Request.URL.Path == "/api/admin/login" ||
		c.Request.URL.Path == "/api/admin/logout" ||
		c.Request.URL.Path == "/api/admin/login/captcha" ||
		c.Request.URL.Path == "/api/user/register" ||
		c.Request.URL.Path == "/api/chat/history" ||
		c.Request.URL.Path == "/api/chat/detail" ||
		c.Request.URL.Path == "/api/chat/list" ||
		c.Request.URL.Path == "/api/app/list" ||
		c.Request.URL.Path == "/api/app/type/list" ||
		c.Request.URL.Path == "/api/app/list/user" ||
		c.Request.URL.Path == "/api/model/list" ||
		c.Request.URL.Path == "/api/mj/imgWall" ||
		c.Request.URL.Path == "/api/mj/notify" ||
		c.Request.URL.Path == "/api/invite/hits" ||
		c.Request.URL.Path == "/api/sd/imgWall" ||
		c.Request.URL.Path == "/api/dall/imgWall" ||
		c.Request.URL.Path == "/api/product/list" ||
		c.Request.URL.Path == "/api/menu/list" ||
		c.Request.URL.Path == "/api/markMap/client" ||
		c.Request.URL.Path == "/api/payment/doPay" ||
		c.Request.URL.Path == "/api/payment/payWays" ||
		c.Request.URL.Path == "/api/suno/detail" ||
		c.Request.URL.Path == "/api/suno/play" ||
		c.Request.URL.Path == "/api/download" ||
		c.Request.URL.Path == "/api/dall/models" ||
		strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
		strings.HasPrefix(c.Request.URL.Path, "/api/payment/notify/") ||
		strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
		strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
		strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
		strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
		strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
		strings.HasPrefix(c.Request.URL.Path, "/static/") ||
		strings.HasPrefix(c.Request.URL.Path, "/swagger/") {
		return false
	}
	return true
}

// 全局异常处理
func errorHandler(c *gin.Context) {
	defer func() {
		if r := recover(); r != nil {
			log.Errorf("Handler Panic：%v", r)
			debug.PrintStack()
			c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed, Message: types.ErrorMsg})
			c.Abort()
		}
	}()
	//加载完 defer recover，继续后续接口调用
	c.Next()
}
